package org.psyduck.security.config;

import cn.hutool.core.annotation.AnnotationUtil;
import org.assertj.core.util.Lists;
import org.psyduck.security.annotation.AnonymousAccess;
import org.psyduck.security.handler.NotLoginHandler;
import org.psyduck.security.handler.PermissionDeniedHandler;
import org.psyduck.security.providers.SmsAuthenticationProvider;
import org.psyduck.security.service.SecurityUserDetailsService;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    NotLoginHandler notLoginHandler;

    @Autowired
    PermissionDeniedHandler permissionDeniedHandler;

    @Autowired
    SecurityUserDetailsService securityUserDetailsService;

    @Autowired
    SmsAuthenticationProvider smsAuthenticationProvider;

    @Autowired
    JwtTokenFilter jwtTokenFilter;

    @Autowired
    RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.authenticationProvider(smsAuthenticationProvider);
        // 设置UserDetailsService
        // 使用BCrypt进行密码的hash
        authenticationManagerBuilder.userDetailsService(securityUserDetailsService).passwordEncoder(passwordEncoder());
    }

    /**
     * 装载BCrypt密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.exceptionHandling().accessDeniedHandler(permissionDeniedHandler).and()
            // 由于使用的是JWT，我们这里不需要csrf
            .csrf().disable()
            .exceptionHandling().authenticationEntryPoint(notLoginHandler).and()
            // 基于token，所以不需要session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

            .authorizeRequests()

            .antMatchers(getAnonymousUrls()).anonymous()
            // 对于获取token的rest api要允许匿名访问
            .antMatchers("/security/**", "/sms/send", "/error/**", "/druid/**", "/magic/web/**", "/ueditor/**","/userfiles/**").permitAll()
            // 除上面外的所有请求全部需要鉴权认证
            .anyRequest().authenticated()

            .accessDecisionManager(new PdAccessDecisionManager(requestMappingHandlerMapping))
        ;

        // 禁用缓存
        httpSecurity.headers().cacheControl();
        // 按了好几次ctrl s
        httpSecurity.headers().frameOptions().disable();

        // 添加JWT filter
        httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);

        // 允许跨域
        httpSecurity.cors();
    }

    private String[] getAnonymousUrls() {
        Set<String> allAnonymousAccess = new HashSet<>();

        Map<String, Object> controllers = applicationContext.getBeansWithAnnotation(RestController.class);
        for (Map.Entry<String, Object> entry : controllers.entrySet()) {
            Class<?> controllerClass = AopUtils.getTargetClass(entry.getValue());
            AnonymousAccess annotation = AnnotationUtil.getAnnotation(controllerClass, AnonymousAccess.class);
            if(null != annotation) {
                Lists.newArrayList((String [])AnnotationUtil.getAnnotationValue(controllerClass, RequestMapping.class)).forEach(it -> {
                    if(!it.startsWith("/")){
                        it = "/" + it;
                    }
                    if(it.endsWith("/")){
                        it += "**";
                    }else{
                        it += "/**";
                    }
                    allAnonymousAccess.add(it);
                });
            }
        }

        // 获取所有的 RequestMapping
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();

        // 循环 RequestMapping
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
            HandlerMethod value = infoEntry.getValue();
            // 获取方法上 AnonymousAccess 类型的注解
            AnonymousAccess methodAnnotation = value.getMethodAnnotation(AnonymousAccess.class);
            // 如果方法上标注了 AnonymousAccess 注解，就获取该方法的访问全路径
            if (methodAnnotation != null) {
                allAnonymousAccess.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
            }
        }
        return allAnonymousAccess.toArray(new String[0]);
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(
"swagger-ui.html",
            "**/swagger-ui.html",
            "/favicon.ico",
            "/**/*.css",
            "/**/*.js",
            "/**/*.png",
            "/**/*.gif",
            "/swagger-resources/**",
            "/v2/**",
            "/**/*.ttf"
        );
        web.ignoring().antMatchers("/v2/api-docs",
            "/swagger-resources/configuration/ui",
            "/swagger-resources",
            "/swagger-resources/configuration/security",
            "/swagger-ui.html"
        );
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 去掉默认前缀ROLE_
     */
    @Bean
    public GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults("");
    }

}